/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: config.c,v 1.12 2005/10/18 09:52:37 pullmoll Exp $
 *****************************************************************************/
#include "osd.h"
#include "config.h"
#include "tools.h"
#include "crypt.h"
#include "memalloc.h"
#include "shmalloc.h"
#include "logger.h"

configuration_t *g_conf = NULL;

static const char *str_loglevel[] = {
	"none",
	"error",
	"normal",
	"minor",
	"debug",
	"debugx",
	NULL
};

static const char *str_logsection[] = {
	"core",
	"mem",
	"shm",
	"sock",
	"crypto",
	"peer",
	"store",
	"file",
	"client",
	"proxy",
	"news",
	NULL
};

typedef struct option_s {
	const char *name;
	int runtime;
}	option_t;

static const option_t opt[] = {
	{"nodename", 1},
	{"nodeport", 0},
	{"bindaddr", 0},
	{"bindport", 0},
	{"niceness", 0},
	{"backlog", 0},
	{"bwlimit", 0},
	{"bwlimit_in", 1},
	{"bwlimit_out", 1},
	{"bwlimit_daily", 1},
	{"runpath", 0},
	{"storepath", 0},
	{"storesize", 0},
	{"storedepth", 0},
	{"maxhtl", 1},
	{"fec_queuesize", 1},
	{"seednodes", 0},
	{"whitelist", 1},
	{"blacklist", 1},
	{"crypto_default", 1},
	{"crypto_reject", 0},
	{"fcphost", 0},
	{"fcpport", 0},
	{"fcpretries", 1},
	{"proxyhost", 0},
	{"proxyport", 0},
	{"proxyhtl", 1},
	{"proxycache", 1},
	{"logfile", 1},
	{"loglevel", 1},
	{"loglevel_mem", 1},
	{"loglevel_shm", 1},
	{"loglevel_sock", 1},
	{"loglevel_crypto", 1},
	{"loglevel_peer", 1},
	{"loglevel_store", 1},
	{"loglevel_file", 1},
	{"loglevel_client", 1},
	{"loglevel_proxy", 1},
	{"loglevel_news", 1},
	{"temmpath", 0},
	{"password", 1},
	{"news_base", 1},
	{"news_boards", 1},
	{"news_ignore", 1},
	{"news_days", 1},
	{"news_tries", 1},
	{"news_skip", 1},
	{"news_nick", 1},
	{"language", 1},
#if	STORE_TYPE == 2
	{"mysql_user", 0},
	{"mysql_pass", 0},
	{"mysql_db", 0},
	{"mysql_host", 0},
	{"mysql_port", 0},
	{"mysql_optimize", 0},
	{"mysql_tablesize", 0},
#endif
	{NULL, 0}
};

static const char *array_str(const char *(*name_func)(size_t indx),
	size_t arr[], size_t nelem)
{
#undef	BUFSIZE
#define BUFSIZE	1024
#undef	BUFNUM
#define	BUFNUM	4
	static char *buff[BUFNUM] = {NULL, NULL, NULL, NULL};
	static int which = 0;
	const char *src;
	char *dst;
	size_t i;

	which = (which + 1) % BUFNUM;
	xfree(buff[which]);
	buff[which] = dst = xcalloc(BUFSIZE, sizeof(char));
	for (i = 0; i < nelem; i++) {
		if (0 == arr[i]) {
			continue;
		}
		src = (*name_func)(i);
		if (NULL == src || *src == '\0')
			break;
		if (dst > buff[which]) {
			dst += pm_spprintf(dst, buff[which], BUFSIZE, ",");
		}
		dst += pm_spprintf(dst, buff[which], BUFSIZE, "%s", src);
	}
	*dst = '\0';
	return buff[which];
}

int conf_init(int argc, char **argv)
{
	char pathname[MAXPATHLEN];
	char *slash;
	size_t len;
	int i;
	FUN("conf_init");

	g_conf = (configuration_t *)xcalloc(1, sizeof(configuration_t));
	/* if xcalloc fails, it will call die(), so we don't have to check... */

	g_conf->niceness = NICE;

	/* sock.c */
	g_conf->backlog = BACKLOG_DEFAULT;

	/* entropy.c */
	pm_snprintf(g_conf->program, sizeof(g_conf->program), "%s",
		PROGNAME);
	pm_snprintf(g_conf->progname, sizeof(g_conf->progname), "%s",
		PROGNAME);
	pm_snprintf(g_conf->progpath, sizeof(g_conf->progpath),
		PROGPATH_DEFAULT);
	pm_snprintf(g_conf->configpath, sizeof(g_conf->configpath),
		CONFIG_PATH);
	pm_snprintf(g_conf->runpath, sizeof(g_conf->runpath),
		RUNPATH_DEFAULT);
	pm_snprintf(g_conf->configfile, sizeof(g_conf->configfile),
		CONFIG_DEFAULT);
	/* override config file location */
	for (i = 1; i < argc; i++) {
		if (0 == strcmp(argv[i], "-c") || 0 == strcmp(argv[i], "--config")) {
			if (i + 1 < argc) {
				pm_snprintf(g_conf->configfile, sizeof(g_conf->configfile), "%s",
					argv[++i]);
			}
		} else if (0 == strncmp(argv[i], "-c", 2)) {
			pm_snprintf(g_conf->configfile, sizeof(g_conf->configfile), "%s",
				&argv[i][2]);
		} else if (0 == strncmp(argv[i], "--config", 8)) {
			pm_snprintf(g_conf->configfile, sizeof(g_conf->configfile), "%s",
				&argv[i][8]);
		}
	}
	pm_snprintf(g_conf->logfile, sizeof(g_conf->logfile),
		LOG_FILE);
	for (i = 0; i < LOGSECTIONS; i++)
		g_conf->loglevel[i] = L_NORMAL;
	pm_snprintf(g_conf->temppath, sizeof(g_conf->temppath), "%s",
		TEMPPATH_DEFAULT);

	/* peer.c */
	pm_snprintf(g_conf->nodename, sizeof(g_conf->nodename), "%s",
		NODE_NAME_DEFAULT);
	g_conf->nodeport = NODE_PORT_DEFAULT;
	pm_snprintf(g_conf->bindaddr, sizeof(g_conf->bindaddr), "%s",
		BIND_ADDR_DEFAULT);
	g_conf->bindport = BIND_PORT_DEFAULT;
	g_conf->maxhtl = MAX_HTL_DEFAULT;
	g_conf->fec_queuesize = FEC_QUEUESIZE_DEFAULT;
	g_conf->bwlimit = BWLIMIT_DEFAULT;
	g_conf->bwlimit_in = BWLIMIT_IN_DEFAULT;
	g_conf->bwlimit_out = BWLIMIT_OUT_DEFAULT;
	g_conf->bwlimit_daily = 0;
	pm_snprintf(g_conf->seednodes, sizeof(g_conf->seednodes),
		SEEDNODES_DEFAULT);
	pm_snprintf(g_conf->whitelist, sizeof(g_conf->whitelist),
		WHITELIST_DEFAULT);
	pm_snprintf(g_conf->blacklist, sizeof(g_conf->blacklist),
		BLACKLIST_DEFAULT);

	/* client.c */
	pm_snprintf(g_conf->fcphost, sizeof(g_conf->fcphost), "%s",
		FCP_HOST_DEFAULT);
	g_conf->fcpport = FCP_PORT_DEFAULT;
	g_conf->fcpretries = FCP_RETRIES_DEFAULT;

	/* proxy.c */
	pm_snprintf(g_conf->proxyhost, sizeof(g_conf->proxyhost), "%s",
		PROXY_HOST_DEFAULT);
	g_conf->proxyport = PROXY_PORT_DEFAULT;
	g_conf->proxyhtl = PROXY_HTL_DEFAULT;
#if	PROXYCACHE
	g_conf->proxycache = PROXYCACHE;
#endif
	g_conf->password[0] = '\0';

	/* news.c */
	pm_snprintf(g_conf->news_base, sizeof(g_conf->news_base), "%s",
		"news");
	pm_snprintf(g_conf->news_boards, sizeof(g_conf->news_boards), "%s",
		"test,boards,public,entropy,gofrost,blackboard,mp3,movies,discussions,stop1984,apple,deutsch,francais,espanol,italiano");
	g_conf->news_ignore[0] = '\0';
	g_conf->news_days = NEWS_DAYS_DEFAULT;
	g_conf->news_tries = NEWS_TRIES_DEFAULT;
	g_conf->news_skip = NEWS_SKIP_DEFAULT;
	pm_snprintf(g_conf->news_nick, sizeof(g_conf->news_nick), "%s",
		"Anonymous");

	/* store.c */
	pm_snprintf(g_conf->storepath, sizeof(g_conf->storepath), "%s",
		STOREPATH_DEFAULT);
	g_conf->storesize = (uint64_t)STORESIZE_DEFAULT;
	g_conf->storedepth = STOREDEPTH_DEFAULT;
	g_conf->storekeys = 1;
	g_conf->storehead = 0;

#if	STORE_TYPE == 2
	/* store_mysql.c */
	pm_snprintf(g_conf->mysql_user, sizeof(g_conf->mysql_user), "%s",
		MYSQL_USER_DEFAULT);
	pm_snprintf(g_conf->mysql_pass, sizeof(g_conf->mysql_pass), "%s",
		MYSQL_PASS_DEFAULT);
	pm_snprintf(g_conf->mysql_db, sizeof(g_conf->mysql_db), "%s",
		MYSQL_DB_DEFAULT);
	pm_snprintf(g_conf->mysql_host, sizeof(g_conf->mysql_host), "%s",
		MYSQL_HOST_DEFAULT);
	g_conf->mysql_port = MYSQL_PORT_DEFAULT;
	g_conf->mysql_optimize = 0;
	g_conf->mysql_tablesize = MYSQL_TABLESIZE_DEFAULT;
#endif

	/* i18n.c */
	pm_snprintf(g_conf->language, sizeof(g_conf->language), "%s",
		"en");

	/* crypt.c */
	pm_snprintf(g_conf->crypto_default, sizeof(g_conf->crypto_default), "%s",
		CRYPTO_DEFAULT);
	memset(g_conf->crypto_reject, 0, sizeof(g_conf->crypto_reject));
	/* reject crypt0 and crypt8 by default */
	g_conf->crypto_reject[0] = 1;
	g_conf->crypto_reject[8] = 1;

	strncpy(g_conf->program, argv[0], sizeof(g_conf->program));
	strncpy(g_conf->progpath, argv[0], sizeof(g_conf->progpath));
	if (NULL != (slash = strrchr(g_conf->progpath, '/'))) {
		slash[1] = '\0';
	} else {
		getcwd(g_conf->progpath, sizeof(g_conf->progpath));
	}

	return 0;
}

#undef	LINESIZE
#define	LINESIZE	1024

/* ARGUSED */
int conf_read_line(char *line, const char *source, configuration_t *p_conf)
{
	char *key, *val, *eol;
	char *toke;
	FUN("conf_read_line");

#if	DEBUG == 0
	(void)source;
#else
	if (NULL == source) {
		source = "[UNKNOWN]";
	}
#endif

	key = val = line;
	while (*val != '\0' && *val != '=')
		val++;
	if (*val == '=') {
		/* terminate key at the first '=' character */
		*val++ = '\0';
		eol = val;
		while (*eol != '\0' && *eol != '\r' && *eol != '\n')
			eol++;
		*eol = '\0';
	} else {
		/* any empty argument is a string of length zero */
		val = "";
	}
	LOG(L_MINOR,("%s: %s=%s\n", source, key, val));
	if (0 == strcasecmp(key, "verbose")) {
		verbose = 1;
#ifdef	__CYGWIN__
	} else if (0 == strcasecmp(key, "autorun")) {
		extern int autorun;
		autorun = 1;
#endif
	} else if (0 == strcasecmp(key, "nodename")) {
		pm_snprintf(g_conf->nodename, sizeof(g_conf->nodename), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->nodename, g_conf->nodename)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->nodename, g_conf->nodename));
		}
	} else if (0 == strcasecmp(key, "nodeport")) {
		g_conf->nodeport = strtoul(val, NULL, 0);
		if (g_conf->nodeport > 65534) {
			LOG(L_ERROR,("Invalid node port number (%d)\n",
				g_conf->nodeport));
			die(1,"Invalid node port number (%d)\n",
				g_conf->nodeport);
		}
		if (NULL != p_conf &&
			p_conf->nodeport != g_conf->nodeport) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->nodeport, g_conf->nodeport));
		}
	} else if (0 == strcasecmp(key, "bindaddr")) {
		pm_snprintf(g_conf->bindaddr, sizeof(g_conf->bindaddr), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->bindaddr, g_conf->bindaddr)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->bindaddr, g_conf->bindaddr));
		}
	} else if (0 == strcasecmp(key, "bindport")) {
		g_conf->bindport = strtoul(val, NULL, 0);
		if (g_conf->bindport > 0 && g_conf->bindport < 1024) {
			LOG(L_ERROR,("You can't use privileged ports (%d)\n",
				g_conf->bindport));
			die(1,"You can't use privileged ports (%d)\n",
				g_conf->bindport);
		} else if (g_conf->bindport > 65534) {
			LOG(L_ERROR,("Invalid bind port number (%d)\n",
				g_conf->bindport));
			die(1,"Invalid bind port number (%d)\n",
				g_conf->bindport);
		}
		if (NULL != p_conf &&
			p_conf->bindport != g_conf->bindport) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->bindport, g_conf->bindport));
		}
	} else if (0 == strcasecmp(key, "niceness")) {
		g_conf->niceness = strtol(val, NULL, 0);
		if (g_conf->niceness < 0) {
			g_conf->niceness = 0;
		} else if (g_conf->niceness > 20) {
			g_conf->niceness = 20;
		}
		if (NULL != p_conf &&
			p_conf->niceness != g_conf->niceness) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->niceness, g_conf->niceness));
		}
	} else if (0 == strcasecmp(key, "backlog")) {
		g_conf->backlog = strtol(val, NULL, 0);
		if (g_conf->backlog < 1) {
			g_conf->backlog = 1;
		} else if (g_conf->backlog > 128) {
			g_conf->backlog = 128;
		}
		if (NULL != p_conf &&
			p_conf->backlog != g_conf->backlog) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->backlog, g_conf->backlog));
		}
	} else if (0 == strcasecmp(key, "bwlimit")) {
		g_conf->bwlimit = strtoul_KMG(val);
		if (NULL != p_conf &&
			p_conf->bwlimit != g_conf->bwlimit) {
			LOG(L_MINOR,("%s changes %s=%u -> %u\n",
				source, key, p_conf->bwlimit, g_conf->bwlimit));
		}
	} else if (0 == strcasecmp(key, "bwlimit_in")) {
		g_conf->bwlimit_in = strtoul_KMG(val);
		if (NULL != p_conf &&
			p_conf->bwlimit_in != g_conf->bwlimit_in) {
			LOG(L_MINOR,("%s changes %s=%u -> %u\n",
				source, key, p_conf->bwlimit_in, g_conf->bwlimit_in));
		}
	} else if (0 == strcasecmp(key, "bwlimit_out")) {
		g_conf->bwlimit_out = strtoul_KMG(val);
		if (NULL != p_conf &&
			p_conf->bwlimit_out != g_conf->bwlimit_out) {
			LOG(L_MINOR,("%s changes %s=%u -> %u\n",
				source, key, p_conf->bwlimit_out, g_conf->bwlimit_out));
		}
	} else if (0 == strcasecmp(key, "bwlimit_daily")) {
		g_conf->bwlimit_daily = strtoul_KMG(val);
		/* If the value is below 1MB, we assume that he meant xxMB, though */
		if (g_conf->bwlimit_daily > 0 &&
			g_conf->bwlimit_daily < 1024 * 1024) {
			g_conf->bwlimit_daily *= 1024 * 1024;
			LOG(L_ERROR,("assuming that you wanted to configure %s=%s\n",
				key, round_KMG(g_conf->bwlimit_daily)));
		}
		if (NULL != p_conf &&
			p_conf->bwlimit_daily != g_conf->bwlimit_daily) {
			LOG(L_MINOR,("%s changes %s=%llu -> %llu\n",
				source, key,
				(long long unsigned)p_conf->bwlimit_daily,
				(long long unsigned)g_conf->bwlimit_daily));
		}
	} else if (0 == strcasecmp(key, "runpath")) {
		pm_snprintf(g_conf->runpath, sizeof(g_conf->runpath), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->runpath, g_conf->runpath)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->runpath, g_conf->runpath));
		}
	} else if (0 == strcasecmp(key, "storepath")) {
		pm_snprintf(g_conf->storepath, sizeof(g_conf->storepath), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->storepath, g_conf->storepath)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->storepath, g_conf->storepath));
		}
	} else if (0 == strcasecmp(key, "storesize")) {
		g_conf->storesize = strtoul_KMG(val);
		if (NULL != p_conf &&
			p_conf->storesize != g_conf->storesize) {
			LOG(L_MINOR,("%s changes %s=%llu -> %llu\n",
				source, key,
				(long long unsigned)p_conf->storesize,
				(long long unsigned)g_conf->storesize));
		}
	} else if (0 == strcasecmp(key, "storedepth")) {
		g_conf->storedepth = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->storedepth != g_conf->storedepth) {
			LOG(L_MINOR,("%s changes %s=%u -> %u\n",
				source, key, p_conf->storedepth, g_conf->storedepth));
		}
#if	STORE_TYPE == 2
	} else if (0 == strcasecmp(key, "mysql_user")) {
		pm_snprintf(g_conf->mysql_user, sizeof(g_conf->mysql_user), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->mysql_user, g_conf->mysql_user)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->mysql_user, g_conf->mysql_user));
		}
	} else if (0 == strcasecmp(key, "mysql_pass")) {
		pm_snprintf(g_conf->mysql_pass, sizeof(g_conf->mysql_pass), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->mysql_pass, g_conf->mysql_pass)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->mysql_pass, g_conf->mysql_pass));
		}
	} else if (0 == strcasecmp(key, "mysql_db")) {
		pm_snprintf(g_conf->mysql_db, sizeof(g_conf->mysql_db), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->mysql_db, g_conf->mysql_db)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->mysql_db, g_conf->mysql_db));
		}
	} else if (0 == strcasecmp(key, "mysql_host")) {
		pm_snprintf(g_conf->mysql_host, sizeof(g_conf->mysql_host), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->mysql_host, g_conf->mysql_host)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->mysql_host, g_conf->mysql_host));
		}
	} else if (0 == strcasecmp(key, "mysql_port")) {
		g_conf->mysql_port = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->mysql_port != g_conf->mysql_port) {
			LOG(L_MINOR,("%s changes %s=%u -> %u\n",
				source, key, p_conf->mysql_port, g_conf->mysql_port));
		}
	} else if (0 == strcasecmp(key, "mysql_tablesize")) {
		g_conf->mysql_tablesize = strtoul_KMG(val);
		if (NULL != p_conf &&
			p_conf->mysql_tablesize != g_conf->mysql_tablesize) {
			LOG(L_MINOR,("%s changes %s=%llu -> %llu\n",
				source, key, p_conf->mysql_tablesize, g_conf->mysql_tablesize));
		}
	} else if (0 == strcasecmp(key, "mysql_optimize")) {
		if (val[0] == '0' || val[0] == '1') {
			g_conf->mysql_optimize = val[0] - '0';
		} else if (0 == strcasecmp(val, "false")) {
			g_conf->mysql_optimize = 0;
		} else if (0 == strcasecmp(val, "true")) {
			g_conf->mysql_optimize = 1;
		} else if (0 == strcasecmp(val, "no")) {
			g_conf->mysql_optimize = 0;
		} else if (0 == strcasecmp(val, "yes")) {
			g_conf->mysql_optimize = 1;
		} else {
			LOG(L_ERROR,("invalid %s=%s\n", key, val));
		}
		if (NULL != p_conf &&
			p_conf->mysql_optimize != g_conf->mysql_optimize) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->mysql_optimize, g_conf->mysql_optimize));
		}
#endif	/* STORE_TYPE == 2 */
	} else if (0 == strcasecmp(key, "maxhtl")) {
		g_conf->maxhtl = strtoul(val, NULL, 0);
		if (g_conf->maxhtl < 2) {
			g_conf->maxhtl = 2;
		} else if (g_conf->maxhtl > MAX_HTL) {
			g_conf->maxhtl = MAX_HTL;
		}
		if (NULL != p_conf &&
			p_conf->maxhtl != g_conf->maxhtl) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->maxhtl, g_conf->maxhtl));
		}
	} else if (0 == strcasecmp(key, "fec_queuesize")) {
		g_conf->fec_queuesize = strtoul(val, NULL, 0);
		if (g_conf->fec_queuesize > 0 && g_conf->fec_queuesize < 2) {
			g_conf->fec_queuesize = 2;
		} else if (g_conf->fec_queuesize > FEC_QUEUESIZE) {
			g_conf->fec_queuesize = FEC_QUEUESIZE;
		}
		if (NULL != p_conf &&
			p_conf->fec_queuesize != g_conf->fec_queuesize) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->fec_queuesize, g_conf->fec_queuesize));
		}
	} else if (0 == strcasecmp(key, "seednodes")) {
		pm_snprintf(g_conf->seednodes, sizeof(g_conf->seednodes), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->seednodes, g_conf->seednodes)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->seednodes, g_conf->seednodes));
		}
	} else if (0 == strcasecmp(key, "whitelist")) {
		pm_snprintf(g_conf->whitelist, sizeof(g_conf->whitelist), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->whitelist, g_conf->whitelist)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->whitelist, g_conf->whitelist));
		}
	} else if (0 == strcasecmp(key, "blacklist")) {
		pm_snprintf(g_conf->blacklist, sizeof(g_conf->blacklist), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->blacklist, g_conf->blacklist)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->blacklist, g_conf->blacklist));
		}
	} else if (0 == strcasecmp(key, "crypto_default")) {
		pm_snprintf(g_conf->crypto_default, sizeof(g_conf->crypto_default),
			"%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->crypto_default, g_conf->crypto_default)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->crypto_default, g_conf->crypto_default));
		}
	} else if (0 == strcasecmp(key, "crypto_reject")) {
		int n;

		memset(g_conf->crypto_reject, 0, sizeof(g_conf->crypto_reject));
		while (*val != '\0' && *val != '\r' && *val != '\n') {
			char ch;
			toke = val;
			while (isalnum(ch = *val))
				val++;
			*val = '\0';
			n = crypto_module_indx(toke);
			if (n >= 0 && n < CRYPTOMODULES) {
				LOG(L_DEBUG,("valid crypto_reject %s, sep %02x\n",
					toke, (unsigned)ch));
				g_conf->crypto_reject[n] = 1;
			} else {
				LOG(L_ERROR,("invalid crypto_reject %s, sep %02x\n",
					toke, (unsigned)ch));
			}
			*val = ch;
			while (*val == ',')
				val++;
		}
		if (NULL != p_conf &&
			0 != memcmp(p_conf->crypto_reject, g_conf->crypto_reject,
				sizeof(g_conf->crypto_reject))) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, 
				array_str(crypto_module_name,
					p_conf->crypto_reject, CRYPTOMODULES),
				array_str(crypto_module_name,
					g_conf->crypto_reject, CRYPTOMODULES)));
		}
	} else if (0 == strcasecmp(key, "fcphost")) {
		pm_snprintf(g_conf->fcphost, sizeof(g_conf->fcphost), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->fcphost, g_conf->fcphost)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->fcphost, g_conf->fcphost));
		}
	} else if (0 == strcasecmp(key, "fcpport")) {
		g_conf->fcpport = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->fcpport != g_conf->fcpport) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->fcpport, g_conf->fcpport));
		}
	} else if (0 == strcasecmp(key, "fcpretries")) {
		g_conf->fcpretries = strtoul(val, NULL, 0);
		if (g_conf->fcpretries < 1) {
			g_conf->fcpretries = 1;
		} else if (g_conf->fcpretries > 20) {
			g_conf->fcpretries = 20;
		}
		if (NULL != p_conf &&
			p_conf->fcpretries != g_conf->fcpretries) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->fcpretries, g_conf->fcpretries));
		}
	} else if (0 == strcasecmp(key, "proxyhost")) {
		pm_snprintf(g_conf->proxyhost, sizeof(g_conf->proxyhost), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->proxyhost, g_conf->proxyhost)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->proxyhost, g_conf->proxyhost));
		}
	} else if (0 == strcasecmp(key, "proxyport")) {
		g_conf->proxyport = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->proxyport != g_conf->proxyport) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->proxyport, g_conf->proxyport));
		}
	} else if (0 == strcasecmp(key, "proxyhtl")) {
		g_conf->proxyhtl = strtoul(val, NULL, 0);
		if (g_conf->proxyhtl > g_conf->maxhtl/4) {
			g_conf->proxyhtl = g_conf->maxhtl/4;
		}
		if (NULL != p_conf &&
			p_conf->proxyhtl != g_conf->proxyhtl) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, p_conf->proxyhtl, g_conf->proxyhtl));
		}
#if	PROXYCACHE
	} else if (0 == strcasecmp(key, "proxycache")) {
		g_conf->proxycache = strtoul(val, NULL, 0);
		if (g_conf->proxycache > PROXYCACHE) {
			g_conf->proxycache = PROXYCACHE;
		}
		if (NULL != p_conf &&
			p_conf->proxycache != g_conf->proxycache) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, (int)p_conf->proxycache, (int)g_conf->proxycache));
		}
#endif
	} else if (0 == strcasecmp(key, "logfile")) {
		if (val[0] == '/') {
			pm_snprintf(g_conf->logfile, sizeof(g_conf->logfile), "%s",
				val);
		} else {
			pm_snprintf(g_conf->logfile, sizeof(g_conf->logfile), "%s%s",
				g_conf->progpath, val);
		}
		if (NULL != p_conf &&
			0 != strcmp(p_conf->logfile, g_conf->logfile)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->logfile, g_conf->logfile));
		}
	} else if (0 == strcasecmp(key, "loglevel")) {
		int i;

		g_conf->loglevel[0] = L_NORMAL;
		if (isdigit(val[0])) {
			g_conf->loglevel[0] = strtoul(val, NULL, 0);
		} else {
			for (i = 0; str_loglevel[i]; i++) {
				if (0 == strcasecmp(val, str_loglevel[i]))
					g_conf->loglevel[0] = i;
			}
		}
		if (NULL != p_conf &&
			p_conf->loglevel[0] != g_conf->loglevel[0]) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key,
				str_loglevel[p_conf->loglevel[0]],
				str_loglevel[g_conf->loglevel[0]]));
		}
	} else if (0 == strncmp(key, "loglevel_", 9)) {
		size_t n;
		for (n = 0; n < LOGSECTIONS; n++) {
			int i;
			if (0 != strcasecmp(key + 9, str_logsection[n]))
				continue;
				g_conf->loglevel[n] = L_NORMAL;
			if (isdigit(val[0])) {
				g_conf->loglevel[n] = strtoul(val, NULL, 0);
			} else {
				for (i = 0; str_loglevel[i]; i++) {
					if (0 == strcasecmp(val, str_loglevel[i]))
						g_conf->loglevel[n] = i;
				}
			}
			if (NULL != p_conf &&
				p_conf->loglevel[n] != g_conf->loglevel[n]) {
				LOG(L_MINOR,("%s changes %s=%s -> %s\n",
					source, key,
					str_loglevel[p_conf->loglevel[n]],
					str_loglevel[g_conf->loglevel[n]]));
			}
		}
	} else if (0 == strcasecmp(key, "temppath")) {
		pm_snprintf(g_conf->temppath, sizeof(g_conf->temppath), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->temppath, g_conf->temppath)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->temppath, g_conf->temppath));
		}
	} else if (0 == strcasecmp(key, "password")) {
		if (0 == strcmp(source, "config")) {
			pm_snprintf(g_conf->password, sizeof(g_conf->password), "%s", val);
			if (NULL != p_conf &&
				0 != strcmp(p_conf->temppath, g_conf->temppath)) {
				LOG(L_MINOR,("%s changes %s=%s -> %s\n",
					source, key, p_conf->password, g_conf->password));
			}
		} else {
			LOG(L_ERROR,("won't change %s from %s\n", key, source));
		}
	} else if (0 == strcasecmp(key, "news_base")) {
		pm_snprintf(g_conf->news_base, sizeof(g_conf->news_base),
			"%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->news_base, g_conf->news_base)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->news_base, g_conf->news_base));
		}
	} else if (0 == strcasecmp(key, "news_boards")) {
		pm_snprintf(g_conf->news_boards, sizeof(g_conf->news_boards),
			"%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->news_boards, g_conf->news_boards)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->news_boards, g_conf->news_boards));
		}
	} else if (0 == strcasecmp(key, "news_ignore")) {
		pm_snprintf(g_conf->news_ignore, sizeof(g_conf->news_ignore),
			"%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->news_ignore, g_conf->news_ignore)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->news_ignore, g_conf->news_ignore));
		}
	} else if (0 == strcasecmp(key, "news_days")) {
		g_conf->news_days = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->news_days != g_conf->news_days) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, (int)p_conf->news_days, (int)g_conf->news_days));
		}
	} else if (0 == strcasecmp(key, "news_tries")) {
		g_conf->news_tries = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->news_tries != g_conf->news_tries) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, (int)p_conf->news_tries, (int)g_conf->news_tries));
		}
	} else if (0 == strcasecmp(key, "news_skip")) {
		g_conf->news_skip = strtoul(val, NULL, 0);
		if (NULL != p_conf &&
			p_conf->news_skip != g_conf->news_skip) {
			LOG(L_MINOR,("%s changes %s=%d -> %d\n",
				source, key, (int)p_conf->news_skip, (int)g_conf->news_skip));
		}
	} else if (0 == strcasecmp(key, "news_nick")) {
		pm_snprintf(g_conf->news_nick, sizeof(g_conf->news_nick), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->news_nick, g_conf->news_nick)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->news_nick, g_conf->news_nick));
		}
	} else if (0 == strcasecmp(key, "language")) {
		pm_snprintf(g_conf->language, sizeof(g_conf->language), "%s", val);
		if (NULL != p_conf &&
			0 != strcmp(p_conf->language, g_conf->language)) {
			LOG(L_MINOR,("%s changes %s=%s -> %s\n",
				source, key, p_conf->language, g_conf->language));
		}
	} else {
		LOG(L_ERROR,("unknown option %s %s=%s\n",
			source, key, val));
	}

	return 0;
}

int conf_read(int argc, char **argv, const char *filename)
{
	configuration_t *p_conf = NULL;
	char *line = NULL;
	char *dst;
	char *env;
	char ch;
	struct stat st;
	int fd = -1;
	int i, rc = 0;
	FUN("conf_read");

	line = xcalloc(1, LINESIZE);

	if (0 == stat(filename, &st)) {
		g_conf->timestamp = st.st_mtime;
	}

	p_conf = (configuration_t *)xmalloc(sizeof(configuration_t));
	memcpy(p_conf, g_conf, sizeof(configuration_t));

	if (-1 == (fd = open(filename, O_RDONLY))) {
		LOG(L_ERROR,("open('%s',O_RDONLY) failed (%s)\n",
			filename, strerror(errno)));
	} else {
		dst = line;
		while (1 == read(fd, &ch, 1)) {
			if (ch != '\r' && ch != '\n') {
				if (dst < &line[LINESIZE-1])
					*dst++ = ch;
				continue;
			}
			*dst = '\0';
			dst = line;
			if (line[0] == '#' || line[0] == '\0')
				continue;
			rc = conf_read_line(line, "config", p_conf);
		}
		close(fd);
		fd = -1;
	}
	xfree(p_conf);

	p_conf = (configuration_t *)xmalloc(sizeof(configuration_t));
	memcpy(p_conf, g_conf, sizeof(configuration_t));
	for (i = 0; opt[i].name; i++) {
		pm_snprintf(line, LINESIZE, "entropy_%s",
			opt[i].name);
		if (NULL != (env = getenv(line))) {
			pm_snprintf(line, LINESIZE, "%s=%s",
				opt[i].name, env);
			conf_read_line(line, "environment", p_conf);
		}
	}
	xfree(p_conf);

	p_conf = (configuration_t *)xmalloc(sizeof(configuration_t));
	memcpy(p_conf, g_conf, sizeof(configuration_t));
	for (i = 1; i < argc; i++) {
		if (0 != strncmp(argv[i], "--", 2))
			continue;
		pm_snprintf(line, LINESIZE, "%s", &argv[i][2]);
		if (i+1 < argc &&
			NULL == strchr(line, '=') && 
			0 != strncmp(argv[i+1], "--", 2)) {
			i++;
			pm_snprintf(line + strlen(line), LINESIZE - strlen(line),
				"=%s", argv[i]);
		}
		conf_read_line(line, "commandline", p_conf);
	}
	xfree(p_conf);

	if (g_conf->runpath[0] != '/') {
		pm_snprintf(line, LINESIZE, "%s", g_conf->runpath);
		if (0 == strncmp(g_conf->runpath, "./", 2)) {
			pm_snprintf(g_conf->runpath, sizeof(g_conf->runpath), "%s%s",
				g_conf->runpath, line + 2);
		} else {
			pm_snprintf(g_conf->runpath, sizeof(g_conf->runpath), "%s%s",
				g_conf->runpath, line);
		}
		/* make an empty runpath the program path */
		if (0 == strlen(g_conf->runpath))
			strcpy(g_conf->runpath, g_conf->progpath);
	}
	if (g_conf->storepath[0] != '/') {
		pm_snprintf(line, LINESIZE, "%s", g_conf->storepath);
		if (0 == strncmp(g_conf->storepath, "./", 2)) {
			pm_snprintf(g_conf->storepath, sizeof(g_conf->storepath), "%s%s",
				g_conf->progpath, line + 2);
		} else {
			pm_snprintf(g_conf->storepath, sizeof(g_conf->storepath), "%s%s",
				g_conf->progpath, line);
		}
	}
	if (g_conf->temppath[0] != '/') {
		pm_snprintf(line, LINESIZE, "%s", g_conf->temppath);
		if (0 == strncmp(g_conf->temppath, "./", 2)) {
			pm_snprintf(g_conf->temppath, sizeof(g_conf->temppath), "%s%s",
				g_conf->progpath, line + 2);
		} else {
			pm_snprintf(g_conf->temppath, sizeof(g_conf->temppath), "%s%s",
				g_conf->progpath, line);
		}
	}
	xfree(line);

	if (-1 != fd) {
		close(fd);
		fd = -1;
	}
	return rc;
}

int conf_update(void)
{
	configuration_t *p_conf = NULL;
	char *line = NULL;
	char *toke = NULL;
	char *dst, *var, *equ;
	struct stat st;
	int fd = -1;
	int e, i, rc = 0;
	char ch;
	int cwd;
	FUN("conf_update");

	cwd = open(".", O_RDONLY);
	if (-1 == cwd) {
		LOG(L_ERROR,("cannot open('.', O_RDONLY) (%s)\n",
			strerror(errno)));
		return -1;
	}
	if (-1 == chdir(g_conf->progpath)) {
		LOG(L_ERROR,("cannot chdir('%s') (%s)\n",
			g_conf->progpath, strerror(errno)));
		return -1;
	}
	if (0 != stat(g_conf->configfile, &st)) {
		e = errno;
		LOG(L_ERROR,("cannot stat config file '%s' (%s)\n",
			g_conf->configfile, strerror(errno)));
		fchdir(cwd);
		close(cwd);
		errno = e;
		return -1;
	}
	if (st.st_mtime == g_conf->timestamp) {
		LOG(L_DEBUG,("config file '%s' unchanged\n",
			g_conf->configfile));
		fchdir(cwd);
		close(cwd);
		return 0;
	}
	LOG(L_NORMAL,("config file change detected (%s)\n",
		datetime_str(st.st_mtime)));
	g_conf->timestamp = st.st_mtime;

	fd = open(g_conf->configfile, O_RDONLY);
	if (-1 == fd) {
		e = errno;
		LOG(L_ERROR,("open('%s', O_RDONLY) failed (%s)\n",
			g_conf->configfile, strerror(errno)));
		fchdir(cwd);
		close(cwd);
		errno = e;
		return -1;
	}

	line = xcalloc(1, LINESIZE);
	toke = xcalloc(1, LINESIZE);

	p_conf = (configuration_t *)xmalloc(sizeof(configuration_t));
	memcpy(p_conf, g_conf, sizeof(configuration_t));

	dst = line;
	while (1 == read(fd, &ch, 1)) {
		if (ch != '\r' && ch != '\n') {
			if (dst < &line[LINESIZE-1])
				*dst++ = ch;
			continue;
		}
		*dst = '\0';
		dst = line;
		if (line[0] == '#' || line[0] == '\0')
			continue;
		strcpy(toke, line);
		equ = var = toke;
		while (*equ != '\0' && *equ != '=')
			equ++;
		if (*equ != '=')
			continue;
		*equ = '\0';
		for (i = 0; NULL != opt[i].name; i++)
			if (0 == strcasecmp(var, opt[i].name))
				break;
		if (NULL == opt[i].name || 1 != opt[i].runtime)
			continue;
		rc = conf_read_line(line, "config", p_conf);
	}
	close(fd);
	fd = -1;
	fchdir(cwd);
	close(cwd);

	xfree(p_conf);
	xfree(line);
	xfree(toke);
	return 0;
}

static const char *path_skip(const char *skip, const char *path)
{
#undef	BUFNUM
#define	BUFNUM	4
	static char *buff[BUFNUM] = {NULL, NULL, NULL, NULL};
	static int which = 0;

	which = (which + 1) % BUFNUM;
	xfree(buff[which]);
	pm_asprintf(&buff[which], "%s", path);
	if (0 == strncmp(buff[which], skip, strlen(skip)))
		strcpy(buff[which], buff[which] + strlen(skip));
	return buff[which];
}

int conf_write(const char *filename)
{
	FILE *fp = NULL;
	size_t n;
	int rc = 0;
	FUN("conf_write");

	if (0 == strcmp(filename, "-")) {
		fp = stdout;
	} else {
		fp = fopen(filename, "w");
		if (NULL == fp) {
			LOG(L_ERROR,("cannot create config file '%s'!\n", filename));
			rc = -EACCES;
			goto cleanup;
		}
	}

	fprintf(fp, "%s%s=%s\n\n",
"# Insert your node's domain name here, or the IP address if you have a\n" \
"# permanent IP. If you are on a dial-up network, you should register\n" \
"# with some free domain name service (like dyndns.org, dhs.org, fdns.net\n" \
"# or whatever you prefer) and register your dynamic IP address.\n" \
"# If your node is not reachable at the specified name/address, you will\n" \
"# have a hard time to retrieve anything from Entropy, because your node\n" \
"# will hardly ever get any keys.\n",
	"nodename", g_conf->nodename); 

	fprintf(fp, "%s%s=%d\n\n",
"# Place your node's listening port number (service) here. Choose a port\n" \
"# number above 1024 and below 65536.\n",
	"nodeport",	g_conf->nodeport); 

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying an IP address to bind to. The default is 0.0.0.0 (IPADDR_ANY).\n",
	"bindaddr",	g_conf->bindaddr); 

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying a (different) port to bind to (default: 0 = same as nodeport).\n",
	"bindport",	g_conf->bindport); 

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying a niceness setting between 0 (normal execution) and 20 (run\n" \
"# only when everything else is idle). Suggested (default) setting is 5.\n",
	"niceness",	g_conf->niceness); 

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying a backlog value for listen() calls for peer, proxy, client.\n" \
"# Defines the number of possible pending socket connections (max. 128).\n",
	"backlog",	g_conf->backlog); 

	fprintf(fp, "%s%s=%s\n\n",
"# You can limit the bandwidth either for both directions, in and out, to\n" \
"# the specified bytes per second value, or use the separate in/out limits.\n",
	"bwlimit", size_KMG(g_conf->bwlimit)); 

	fprintf(fp, "%s%s=%s\n\n",
"# This is used only if bwlimit is set to zero (0) and specifies the input\n" \
"# bandwidth limit in bytes per second.\n",
	"bwlimit_in", size_KMG(g_conf->bwlimit_in)); 

	fprintf(fp, "%s%s=%s\n\n",
"# This is used only if bwlimit is set to zero (0) and specifies the output\n" \
"# bandwidth limit in bytes per second.\n",
	"bwlimit_out", size_KMG(g_conf->bwlimit_out)); 

	fprintf(fp, "%s%s=%s\n\n",
"# If you specify a daily bwlimit greater than zero, then Entropy will\n" \
"# shut down all connections once it reaches this limit within 24 hours.\n",
	"bwlimit_daily", size_KMG(g_conf->bwlimit_daily)); 

	fprintf(fp, "%s%s=%s\n\n",
"# Select a run path where Entropy can keep its pid and sem files.\n" \
"# The default is to keep them in the directory ./\n",
	"runpath", path_skip(g_conf->progpath, g_conf->runpath));

	fprintf(fp, "%s%s=%s\n\n",
"# You must specify a pathname for the data store, where the tree of\n" \
"# directories with data files shall be created. Default is store.\n",
	"storepath", path_skip(g_conf->progpath, g_conf->storepath));

	fprintf(fp, "%s%s=%sB\n\n",
"# How big should the data store grow? Use K for kilo, M for mega\n" \
"# or G for giga bytes. The default of 512MB is a useful value, but\n" \
"# you are of course welcome to spend more storage for the network.\n",
	"storesize", size_KMG(g_conf->storesize));

	fprintf(fp, "%s%s=%d\n\n",
#if	STORE_TYPE == 0
"# How many data store files should the monolithic store code use?\n" \
"# BEWARE: This is rounded up to the next useful value, depending on\n" \
"# your total storesize=. A single store file can have a maximum size\n" \
"# of slightly above 1GB. Thus, if you specify storesize=4GB and have\n" \
"# storedepth=1, you will end up with 4 files, though. You cannot sepcify\n" \
"# more than 256 files and files won't become smaller than 4MB.\n",
#endif
#if STORE_TYPE == 1
"# How many levels of directories should the store tree use?\n" \
"# BEWARE: If you change this, your old store contents is not found,\n" \
"# unless you move the files into the correct depth of your store tree.\n" \
"# As a rule of thumb, you may set this to 0 for very small stores,\n" \
"# set it to 1 for most stores up to several GB, and set it two 2 for\n" \
"# huge stores. You will have a tree of 16*16 directories in this case.\n",
#endif
#if STORE_TYPE == 2
"# How many tables do you want for the store_mysql data store?\n" \
"# To allow for optimizing tables in low HDD space situations, a single\n" \
"# data store table (storeXX in the database) is limited to 1GB.\n" \
"# This limit is hard coded for now.\n",
#endif
	"storedepth", g_conf->storedepth);

	fprintf(fp, "%s%s=%s\n\n",
"# What crypto method to use as default outgoing method?\n" \
"# Currently supported (as of " PACKAGE_STRING ") are:\n" \
"# crypt0: no encryption at all (NULL)\n" \
"# crypt1: 'simple and stupid' xor and swap algorithm\n" \
"# crypt2: two level nested 'prime clock'\n" \
"# crypt3: simple S-Box encryption by Mr. Ikg\n" \
"# vmc1: 'Virtual Machine Crypto', executes a virtual CPUs microcode.\n" \
"# ikg2: Mr. Ikg's 2nd stream cipher alogrithm (modified S-Box).\n" \
"# ikg3: Mr. Ikg's 2nd algo, different entropy vector.\n" \
"# lorenz: 'Lorenz Attractor', based on chaos theory (butterfly effect).\n" \
"# twofish: A complex block cipher from the AES contest\n" \
"# incr: A cipher that transfers PRNG data with net data (uses 2*bandwidth)\n",
	"crypto_default", g_conf->crypto_default);

	fprintf(fp, "%s%s=%s\n\n",
"# What crypto modules to reject for incoming connections?\n" \
"# If you list modules here, your node will reject connections trying to\n" \
"# use the specified module. The modules crypt0, crypt1 and incr could\n" \
"# be listed here, but they aren't (currently) used anyways.\n",
	"crypto_reject",
	array_str(crypto_module_name, g_conf->crypto_reject, CRYPTOMODULES));

	fprintf(fp, "%s%s=%s\n\n",
"# Select a log filename. Default is entropy.log in the current directory.\n",
	"logfile", path_skip(g_conf->progpath, g_conf->logfile));

	fprintf(fp, "%s%s=%s\n\n",
"# Select a logging level from none, error, normal, minor or debug.\n" \
"# none - no reporting at all. You won't see error messages either.\n" \
"# error - only report (severe) errors.\n" \
"# normal - report normal messages, nothing too detailed.\n" \
"# minor - report minor events. This produces a lot of output already.\n" \
"# debug - report each and every detail. This will slow down your node.\n",
	"loglevel", str_loglevel[g_conf->loglevel[0]]);

	for (n = 1; n < LOGSECTIONS; n++) {
		fprintf(fp,
			"# Separate loglevel for %s functions\n%s%s=%s\n",
			str_logsection[n],
			"loglevel_", str_logsection[n],
			str_loglevel[g_conf->loglevel[n]]);
	}
	fprintf(fp, "\n");

	fprintf(fp, "%s%s=%s\n\n",
"# Select a filename for the list of nodes to try to contact initally.\n" \
"# The default is 'seed.txt' and you should have copied/edited this file.\n",
	"seednodes", path_skip(g_conf->progpath, g_conf->seednodes));

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a whitelist of peer IPs and port numbers to allow.\n" \
"# If this file exists and contains entries, no other node will be\n" \
"# accepted in inbound or contacted in outbound direction.\n",
	"whitelist", path_skip(g_conf->progpath, g_conf->whitelist));

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a blacklist of peer IPs and port numbers to deny.\n" \
"# The listed peer IPs and port numbers are permanently blocked\n" \
"# from makeing inbound connections, and won't be contacted.\n",
	"blacklist", path_skip(g_conf->progpath, g_conf->blacklist));

	fprintf(fp, "%s%s=%d\n\n",
"# Clipping value for the hops-to-live (HTL) value for packet messages.\n" \
"# Use something between 2 and 25, or even better leave it as it is.\n",
	"maxhtl", g_conf->maxhtl);

	fprintf(fp, "%s%s=%d\n\n",
"# Set the size for the background execution FEC message queue. You want\n" \
"# to set this low (less than 5 or even 0) if you are on a slow connection.\n" \
"# Choose a high (256) value if your node runs without much local activity.\n",
	"fec_queuesize", g_conf->fec_queuesize);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying the IP where the FCP (Freenet Client Protocol) server should\n" \
"# listen. This is usually 127.0.0.1 or localhost, unless you want to run\n" \
"# only one Node in a local (or even public, but that's not wise)\n" \
"# network. In that case, specify the IP address of the interface,\n" \
"# where your local machines are going through (e.g. 192.168.0.1).\n" \
"# If you're really insane, you can specify 0.0.0.0 for IPADDRANY and\n" \
"# that means that everyone can use your FCP server to retrieve or\n" \
"# insert content into or from ENTROPY.\n",
	"fcphost", g_conf->fcphost);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the port number (service) where the FCP server should look\n" \
"# for incoming requests. This is usually 8482 (which happens to be\n" \
"# the default for Freenet 8481 plus 1).\n",
	"fcpport", g_conf->fcpport);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the number of retries that should be applied whenever Entropy\n" \
"# tries to request or insert a file. The probability of success increases\n" \
"# with higher values. On the other hand higher values make searches for\n" \
"# non-existing keys (e.g. Frost) slower, too.\n",
	"fcpretries", g_conf->fcpretries);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying the IP where the HTTP proxy should listen. This is usually\n" \
"# 127.0.0.1 or localhost, unless you want allow access to your proxy from\n" \
"# outside addresses, too. In that case you might use 0.0.0.0\n",
	"proxyhost", g_conf->proxyhost);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the port number (service) where the HTTP proxy should look\n" \
"# for incoming requests. This is usually 9999, but you can choose any\n" \
"# unused port number above 1024.\n",
	"proxyport", g_conf->proxyport);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the default hops-to-live value that the HTTP proxy should\n" \
"# use for requests. This is usually rather low, compared to the maxhtl\n" \
"# value. If the network weather is bad, you can try to increase it up\n" \
"# to the value of maxhtl. You can set it higher, but that's senseless.\n",
	"proxyhtl", g_conf->proxyhtl);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the maximum number of entries in the Entropy proxy's\n" \
"# temporary cache. This can be any number between 0 (no caching) and\n" \
"# 64  (PROXYCACHE from include/config.h). The proxy cache holds meta\n" \
"# data from files or 'map spaces' and improves the browser throughput.\n",
	"proxycache", (int)g_conf->proxycache);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying the pathname for temporary files. The path must be accessible\n" \
"# for the user running entropy. It will hold temporary files during data\n" \
"# insertion or retrieval.\n",
	"temppath", path_skip(g_conf->progpath, g_conf->temppath));

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a password that is required to modify any settings using the\n" \
"# proxy page /node/config.html\n" \
"# You (really) should set this, if your node is listening for proxy\n" \
"# connections on anything but 127.0.0.1 or localhost.\n",
	"password", g_conf->password);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a message board base name. This name is meant to be changed\n" \
"# only if you want to switch over to an entirely different news area.\n" \
"# The default is 'news' w/o the quotes and you won't find much elswhere.\n",
	"news_base", g_conf->news_base);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a comma separated list of board names to be scanned.\n" \
"# You can check the board announcements on the gateway page or get\n" \
"# board names from another source. The names are case-insensitive.\n",
	"news_boards", g_conf->news_boards);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying a comma separated list of board names to be ignored\n" \
"# in the board announcements. The names are case-insensitive.\n",
	"news_ignore", g_conf->news_ignore);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the number of days to search backwards for news.\n" \
"# Set this to low values if you run Entropy almost daily, since this\n" \
"# makes scanning go a lot faster.\n",
	"news_days", (int)g_conf->news_days);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the number of retries per message number, before the scanner\n" \
"# goes back to the previous day, then to the next board. Keep this low\n" \
"# when there are good network conditions or if you want to have quick\n" \
"# first results where harder to retrieve messages come in later.\n",
	"news_tries", (int)g_conf->news_tries);

	fprintf(fp, "%s%s=%d\n\n",
"# Specifying the number of missing messages to skip and still search for\n" \
"# continuations in the sequentially numbered messages per day. This can\n" \
"# be set to a low value while there are good network conditions.\n",
	"news_skip", (int)g_conf->news_skip);

	fprintf(fp, "%s%s=%s\n\n",
"# You may choose a nickname or pseudonym for news. Entropy will fill in\n" \
"# this name into replies or posts you upload. You can still change your\n" \
"# nick on-the-fly; this is only a default value to put in the fields.\n",
	"news_nick", g_conf->news_nick);

	fprintf(fp, "%s%s=%s\n\n",
"# Specifying the language for loading localization settings and a specific\n" \
"# gateway.<language>.html page at startup.\n",
	"language", g_conf->language);

/* only for the mysql store type */
#if	STORE_TYPE == 2
	fprintf(fp, "%s%s=%s\n\n",
"# All the options below this point are for the MySQL data store.  If you\n" \
"# are not using MySQL, you can stop now.\n\n" \
"# The login name you use to access MySQL.  This user needs to have\n" \
"# permission to create, drop, and optimize tables, and to insert, select,\n" \
"# update, and delete data in MySQL.\n",
	"mysql_user", g_conf->mysql_user);

	fprintf(fp, "%s%s=%s\n\n",
"# The password for mysql_user\n",
	"mysql_pass", g_conf->mysql_pass);

	fprintf(fp, "%s%s=%s\n\n",
"# The name of the database Entropy will store its tables in.  You have to\n" \
"# create this database before running Entropy.\n",
	"mysql_db", g_conf->mysql_db);

	fprintf(fp, "%s%s=%s\n\n",
"# If your database server is not running on the same computer as your\n" \
"# Entropy node, enter the database server's hostname here.\n",
	"mysql_host", g_conf->mysql_host);

	fprintf(fp, "%s%s=%d\n\n",
"# The port number MySQL is using.  If you are using the default MySQL\n" \
"# port, you can just leave this set to zero.\n",
	"mysql_port", g_conf->mysql_port);

	fprintf(fp, "%s%s=%d\n\n",
"# Set this flag to non-zero (or true, or yes) to enable optimization of\n" \
"# your MySQL tables during node start-up. Optimizing will take some time.\n",
	"mysql_optimize", g_conf->mysql_optimize);

	fprintf(fp, "%s%s=%s\n\n",
"# Set the maximum size for a single mysql table, i.e. the storeXX tables.\n" \
"# If you change this, be sure to have stordepth= set to the number of\n" \
"# tables you had before (check with mysql). The default table size is 1GB.\n",
	"mysql_tablesize", size_KMG(g_conf->mysql_tablesize));
#endif

	if (0 != strcmp(filename, "-")) {
		fclose(fp);
	}
	fp = NULL;

cleanup:
	if (NULL != fp) {
		fclose(fp);
		fp = NULL;
	}
	return rc;
}
